As you have learned, Azure’s Table service stores your data
in one or more tables. Each table is a logically distinct domain. You
cannot create queries that span tables. Currently, there is no limit on
the number of tables you can create.
Every Azure table operation has a RESTful request + response with
a corresponding wrapper in the storage client. All REST traffic is
encoded using Atom Publishing Protocol.
The following code shows the request HTTP headers and body for
creating a simple ContactsTable operation. Note that there is no
schema specified anywhere. Properties are defined at the individual
entity level, and not at the table level. The lines that specify
authentication and the table name are highlighted. Authentication is
performed using the SharedKeyLite
scheme discussed in previous chapters.
POST /Tables HTTP/1.1
User-Agent: Microsoft ADO.NET Data Services
x-ms-date: Mon, 20 Apr 2009 17:30:08 GMT
Authorization: SharedKeyLite sriramk:mQrl9rffHDUKKPEEfUyZdLvKWTT0a8o3jvaeoS8QMIU=
Accept: application/atom+xml,application/xml
Accept-Charset: UTF-8
DataServiceVersion: 1.0;NetFx
MaxDataServiceVersion: 1.0;NetFx
Content-Type: application/atom+xml
Host: sriramk.table.core.windows.net
Content-Length: 494
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<entry xmlns:d=http://schemas.microsoft.com/ado/2007/08/dataservices
xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
xmlns="http://www.w3.org/2005/Atom">
<title />
<updated>2009-04-20T17:30:08.533Z</updated>
<author>
<name />
</author>
<id />
<content type="application/xml">
<m:properties>
<d:TableName>ContactTable</d:TableName>
</m:properties>
</content>
</entry>
If everything went well, the server will respond with a message
such as the following:
HTTP/1.1 201 Created
Cache-Control: no-cache
Content-Type: application/atom+xml;charset=utf-8
Location: http://sriramk.table.core.windows.net/Tables('ContactTable')
Server: Table Service Version 1.0 Microsoft-HTTPAPI/2.0
x-ms-request-id: cd54647a-140a-4085-b269-cceb86551005
Date: Mon, 20 Apr 2009 17:29:00 GMT
Content-Length: 797
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<entry xml:base=http://sriramk.table.core.windows.net/
xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
xmlns:m=http://schemas.microsoft.com/ado/2007/08/dataservices/metadata
xmlns="http://www.w3.org/2005/Atom">
<id>http://sriramk.table.core.windows.net/Tables('ContactTable')</id>
<title type="text"></title>
<updated>2009-04-20T17:29:01Z</updated>
<author>
<name />
</author>
<link rel="edit" title="Tables" href="Tables('ContactTable')" />
<category term="sriramk.Tables"
scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
<content type="application/xml">
<m:properties>
<d:TableName>ContactTable</d:TableName>
</m:properties>
</content>
</entry>
Note:
Why use the SharedKeyLite authentication scheme? Why not
use the same authentication scheme as blobs and queues? This stems
from the way ADO.NET Data Services is implemented. In blobs and
queues, signing takes place as the last step, and has access to all
the headers. However, ADO.NET Data Services doesn’t give access to all
the headers through a “hook” that could let the same kind of signing
happen. Hence, a variant of the standard authentication scheme was
devised for use in the Table service.
If you’re the kind of person who likes to write XML parsing code
all day, you can probably skip the following discussion (and apply for a
job on the Windows Azure team—a ton of XML parsing is done on that
team!). However, the rest of us mortals will probably use a client
library. If you’re a .NET developer, the obvious choice is to use the
official storage client library.
What if you’re not a .NET developer? Unlike blobs and queues, not
many open source libraries talk to Azure’s Table service. This is
partially because a lot of Atom parsing must be done. That is also why
this discussion doesn’t walk through a Python or Ruby sample. However,
expect this situation to change as the Table service becomes more
popular.
Let’s build a simple Contacts
table. If you’re familiar with Object Relational
Mapping (ORM) on .NET, the rest of this will sound
familiar.
You start by creating a Contact
class as shown in Example 1.
Example 1. Simple Contacts table
public class Contact : TableServiceEntity { public Contact(string partitionKey, string rowKey) : base(partitionKey, rowKey) { }
public Contact() : base() { PartitionKey = Guid.NewGuid().ToString(); RowKey = String.Empty; }
public string Name { get; set; }
public string Address { get; set; } }
|
This inherits from Microsoft.WindowsAzure.StorageClient.TableServiceEntity,
which is a class in the storage client library that takes care of a lot
of the plumbing for you. You define a PartitionKey and a RowKey in this class. The partition key is
always a new GUID, and you use an empty row key. This implies that every
partition will have only one row that is perfectly fine. We will examine
the subject of partitioning shortly, so don’t worry if this sounds fuzzy
now. You also define some simple properties: Name and Address.
Note that although Azure’s Table service doesn’t have any schema
during creation of the table, you must define your schema while using
the client library upfront. You must now create a wrapper DataServiceContext-derived type to enable you
to create and execute queries.
If you are used to calling ADO.NET Data Services against the
Entity Framework, you know this is automatically generated. However, in
the case of Azure’s Table service, the plumbing to make it automatically
generated doesn’t exist, and you must manually write it. However,
they’re very simple to author, and you can use a template from which you
can copy and paste.
Example 2 shows a
simple ContactDataServiceContext
class that does everything required. Note that the name of the table is
specified inside this class.
Example 2. Simple DataServiceContext-derived class
class ContactDataServiceContext : TableServiceContext { internal ContactDataServiceContext (string baseAddress, StorageCredentials credentials) : base(baseAddress, credentials) { }
internal const string ContactTableName = "ContactTable";
public IQueryable<Contact> ContactTable { get { return this.CreateQuery<Contact>(ContactTableName); } } }
|
With all the data-modeling table code in place, it is now time to
create your tables. You typically place this code inside a startup
script, or some code that is executed when your application launches.
The code shown in Example 3 does two things.
It first checks whether the table exists, and then creates a
table.
Example 3. Creating the table
var account = CloudStorageAccount.Parse(ConfigurationSettings.AppSettings ["DataConnectionString"]); account.CreateCloudTableClient().CreateTableIfNotExist("ContactTable");
|
If you use Fiddler/Netmon/Wireshark to peek at the HTTP traffic,
you should first see the following HTTP request-response pair. But after
that, you would see that the actual table-creation traffic is identical
to the REST messages you saw earlier in this chapter.
HTTP request for table existence check(abbreviated)
GET /Tables('ContactTable') HTTP/1.1
HTTP response (abbreviated)
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<error xmlns="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
<code>ResourceNotFound</code>
<message xml:lang="en-US">The specified resource does not exist.</message>
</error>